package tcln.gxtx.client.utils.lang;

import java.util.HashMap;
import java.util.Map;

/**
 * String utility class for GWT client-side.
 * 
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public final class GwtStringUtils {

    /**
     * Empty string.
     */
    public static final String EMPTY = "";

    /**
     * Simple date pattern for date formatting.
     */
    public static final String SIMPLE_DATE_PARTTERN = "dd.MM.yyyy";

    private static String accentChars = "";
    private static String nonAccentChars = "";

    private static final Map<Character, Character> ACCENT_CHAR_MAP = new HashMap<Character, Character>();
    static {
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 192), 'A');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 193), 'A');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 194), 'A');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 195), 'A');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 196), 'A');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 197), 'A');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 198), 'A');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 199), 'C');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 200), 'E');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 201), 'E');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 202), 'E');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 203), 'E');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 204), 'I');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 205), 'I');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 206), 'I');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 207), 'I');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 208), 'D');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 209), 'N');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 338), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 210), 'O');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 339), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 211), 'O');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 212), 'O');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 213), 'O');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 214), 'O');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 216), 'O');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 217), 'U');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 218), 'U');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 219), 'U');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 220), 'U');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 221), 'Y');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 222), 'T');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 223), 'B');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 352), 's');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 224), 'a');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 353), 's');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 225), 'a');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 226), 'a');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 227), 'a');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 228), 'a');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 229), 'a');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 230), 'a');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 231), 'c');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 232), 'e');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 233), 'e');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 234), 'e');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 235), 'e');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 236), 'i');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 237), 'i');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 238), 'i');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 239), 'i');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 241), 'n');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 242), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 243), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 244), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 245), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 246), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 376), 'y');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 248), 'o');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 249), 'u');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 250), 'u');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 251), 'u');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 252), 'u');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 381), 'z');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 253), 'y');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 382), 'z');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 254), 'y');
        ACCENT_CHAR_MAP.put(Character.valueOf((char) 255), 'y');

        for (Character accent : ACCENT_CHAR_MAP.keySet()) {
            // init once, a few loops, not a problem
            accentChars += accent;
            nonAccentChars += ACCENT_CHAR_MAP.get(accent);
        }
    }

    /**
     * Gets the <code>toString</code> of an <code>Object</code> returning an empty string ("") if <code>null</code>
     * input.
     * 
     * @param obj
     *            the Object to <code>toString</code>, may be null
     * @return the passed in Object's toString, or empty string if <code>null</code> input.
     */
    public static String toString(Object obj) {
        if (obj == null) {
            return "";
        }
        return obj.toString();
    }

    /**
     * Checks if a String is empty ("") or null.
     * 
     * @param str
     *            the String to check, may be null
     * @return <code>true</code> if the String is empty or null
     */
    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }

    /**
     * Checks if a String is not empty ("") and not null.
     * 
     * @param str
     *            the String to check, may be null
     * @return <code>true</code> if the String is not empty and not null
     */
    public static boolean isNotEmpty(final String str) {
        return !isEmpty(str);
    }

    /**
     * Checks if a String is whitespace, empty ("") or null.
     * 
     * @param str
     *            the String to check, may be null
     * @return <code>true</code> if the String is null, empty or whitespace
     */
    public static boolean isBlank(final String str) {
        // this code is taken form commons-lang StringUtils
        if ((str == null) || (str.length() == 0)) {
            return true;
        }
        int strLen = str.length();
        for (int i = 0; i < strLen; i++) {
            if (' ' != str.charAt(i)) { // On the GUI, use can only enter whitespace
                return false;
            }
        }
        return true;
    }

    /**
     * Checks if a String is not empty (""), not null and not whitespace only.
     * 
     * @param str
     *            the String to check, may be null
     * @return <code>true</code> if the String is not empty and not null and not whitespace
     */
    public static boolean isNotBlank(final String str) {
        // this code is taken form commons-lang StringUtils
        return !isBlank(str);
    }

    /**
     * Uncapitalizes a String changing the first letter to title case as per {@link Character#toLowerCase(char)}. No
     * other letters are changed.
     * <p>
     * A <code>null</code> input String returns <code>null</code>.
     * 
     * @param str
     *            Source string.
     * @return the uncapitalized String, <code>null</code> if null String input
     */
    public static String uncapitalize(final String str) {
        if (str == null || str.length() == 0) {
            return str;
        }
        return new StringBuffer(str.length()).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1))
                .toString();
    }

    /**
     * Capitalizes a String changing the first letter to title case as per {@link Character#toTitleCase(char)}. No other
     * letters are changed.
     *<p>
     * A <code>null</code> input String returns <code>null</code>.
     * 
     * @param str
     *            the String to capitalize, may be null
     * @return the capitalized String, <code>null</code> if null String input
     */
    public static String capitalize(final String str) {
        if (str == null || str.length() == 0) {
            return str;
        }
        return new StringBuffer(str.length()).append(Character.toUpperCase(str.charAt(0))).append(str.substring(1))
                .toString();
    }

    /**
     * Gets the simple name (without packapage) of a given class.
     * <p>
     * Because until GWT 2.0.3 method {@link Class#getSimpleName()} is not supported, we have to extract this
     * information by hand.
     * 
     * @param cls
     *            Class to extract.
     * @return Simple name of given class or empty string.
     * @since GWT 2.0.3
     */
    public static String getClassSimpleName(Class<?> cls) {
        if (cls != null) {
            return getClassSimpleName(cls.getName());
        }
        return "";
    }

    /**
     * Gets the simple name (without packapage) of a given class full-name.
     * <p>
     * Because until GWT 2.0.3 method {@link Class#getSimpleName()} is not supported, we have to extract this
     * information by hand.
     * 
     * @param className
     *            Classname (full) to extract.
     * @return Simple name of given classname or empty string.
     * @since GWT 2.0.3
     */
    public static String getClassSimpleName(String className) {
        if (isNotEmpty(className)) {
            int lastDotIndex = className.lastIndexOf(".");
            className = className.substring(lastDotIndex + 1);
            return className;
        } else {
            return "";
        }
    }

    /**
     * Gets the packapage of a given class.
     * <p>
     * Because until GWT 2.0.3 method Class.getPackage() is not supported, we have to extract this information by hand.
     * 
     * @param cls
     *            Class to extract.
     * @return Simple name of given class or <code>null</code>.
     * @since GWT 2.0.3
     */
    public static String getClassPackageName(final Class<?> cls) {
        if (cls != null) {
            String name = cls.getName();
            int lastDotIndex = name.lastIndexOf(".");
            name = name.substring(0, lastDotIndex);
            return name;
        } else {
            return "";
        }
    }

    /**
     * Gets the leftmost <code>len</code> characters of a String.
     * <p>
     * If <code>len</code> characters are not available, or the String is <code>null</code>, the String will be returned
     * without an exception. An exception is thrown if len is negative.
     * 
     * <pre>
     * left(null, *)    = null
     * left(*, -ve)     = ""
     * left("", *)      = ""
     * left("abc", 0)   = ""
     * left("abc", 2)   = "ab"
     * left("abc", 4)   = "abc"
     * </pre>
     * 
     * @param str
     *            the String to get the leftmost characters from, may be null
     * @param len
     *            the length of the required String, must be zero or positive
     * @return the leftmost characters, <code>null</code> if null String input
     */
    public static String left(final String str, final int len) {
        if (str == null) {
            return null;
        }
        if (len < 0) {
            return EMPTY;
        }
        if (str.length() <= len) {
            return str;
        }
        return str.substring(0, len);
    }

    /**
     * Gets the rightmost <code>len</code> characters of a String.
     * <p>
     * If <code>len</code> characters are not available, or the String is <code>null</code>, the String will be returned
     * without an an exception. An exception is thrown if len is negative.
     * 
     * <pre>
     * right(null, *)    = null
     * right(*, -ve)     = ""
     * right("", *)      = ""
     * right("abc", 0)   = ""
     * right("abc", 2)   = "bc"
     * right("abc", 4)   = "abc"
     * </pre>
     * 
     * @param str
     *            the String to get the rightmost characters from, may be null
     * @param len
     *            the length of the required String, must be zero or positive
     * @return the rightmost characters, <code>null</code> if null String input
     */
    public static String right(final String str, final int len) {
        if (str == null) {
            return null;
        }
        if (len < 0) {
            return EMPTY;
        }
        if (str.length() <= len) {
            return str;
        }
        return str.substring(str.length() - len);
    }

    /**
     * Compares 2 strings case-insensitive.
     * 
     * @param s1
     * @param s2
     * @return
     */
    public static int compareIgnoreCase(String s1, String s2) {
        if (s1 == null && s2 == null) {
            return 0;
        }
        if (s1 == null && s2 != null) {
            return -1;
        }
        if (s2 == null && s1 != null) {
            return 1;
        }
        return s1.compareToIgnoreCase(s2);
    }

    /**
     * Counts the number of words from given string. This method uses native implementation supported by Javascript
     * which is considered faster.
     * 
     * @param sourceText
     *            Source text to get word count.
     * @return Word count.
     */
    public static native int countWords(String sourceText) /*-{  
        if (sourceText) {
            var wc = sourceText.match(/\b/g);
            return wc ? wc.length/2:0;  
        }
        return 0;
    }-*/;

    /**
     * <i>Note: This method is copied from {@link org.apache.commons.lang.StringUtils} due to the nature of GWT Emulated
     * JRE.</i>
     * <p>
     * Replaces multiple characters in a String in one go. This method can also be used to delete characters.
     * 
     * <p>
     * For example:<br />
     * <code>replaceChars(&quot;hello&quot;, &quot;ho&quot;, &quot;jy&quot;) = jelly</code>.
     * </p>
     * 
     * <p>
     * A <code>null</code> string input returns <code>null</code>. An empty ("") string input returns an empty string. A
     * null or empty set of search characters returns the input string.
     * </p>
     * 
     * <p>
     * The length of the search characters should normally equal the length of the replace characters. If the search
     * characters is longer, then the extra search characters are deleted. If the search characters is shorter, then the
     * extra replace characters are ignored.
     * </p>
     * 
     * <pre>
     * replaceChars(null, *, *)           = null
     * replaceChars("", *, *)             = ""
     * replaceChars("abc", null, *)       = "abc"
     * replaceChars("abc", "", *)         = "abc"
     * replaceChars("abc", "b", null)     = "ac"
     * replaceChars("abc", "b", "")       = "ac"
     * replaceChars("abcba", "bc", "yz")  = "ayzya"
     * replaceChars("abcba", "bc", "y")   = "ayya"
     * replaceChars("abcba", "bc", "yzx") = "ayzya"
     * </pre>
     * 
     * @param str
     *            String to replace characters in, may be null
     * @param searchChars
     *            a set of characters to search for, may be null
     * @param replaceChars
     *            a set of characters to replace, may be null
     * @return modified String, <code>null</code> if null string input
     */
    public static String replaceChars(String str, String searchChars, String replaceChars) {
        if (isEmpty(str) || isEmpty(searchChars)) {
            return str;
        }
        if (replaceChars == null) {
            replaceChars = EMPTY;
        }
        boolean modified = false;
        int replaceCharsLength = replaceChars.length();
        int strLength = str.length();
        StringBuffer buf = new StringBuffer(strLength);
        for (int i = 0; i < strLength; i++) {
            char ch = str.charAt(i);
            int index = searchChars.indexOf(ch);
            if (index >= 0) {
                modified = true;
                if (index < replaceCharsLength) {
                    buf.append(replaceChars.charAt(index));
                }
            } else {
                buf.append(ch);
            }
        }
        if (modified) {
            return buf.toString();
        }
        return str;
    }

    /**
     * <i>Note: This method is copied from {@link org.apache.commons.lang.StringUtils} due to the nature of GWT Emulated
     * JRE.</i>
     * <p>
     * Joins the elements of the provided array into a single String containing the provided list of elements.
     * </p>
     * 
     * <p>
     * No delimiter is added before or after the list. A <code>null</code> separator is the same as an empty String
     * (""). Null objects or empty strings within the array are represented by empty strings.
     * </p>
     * 
     * <pre>
     * StringUtils.join(null, *)                = null
     * StringUtils.join([], *)                  = ""
     * StringUtils.join([null], *)              = ""
     * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
     * StringUtils.join(["a", "b", "c"], null)  = "abc"
     * StringUtils.join(["a", "b", "c"], "")    = "abc"
     * StringUtils.join([null, "", "a"], ',')   = ",,a"
     * </pre>
     * 
     * @param array
     *            the array of values to join together, may be null
     * @param separator
     *            the separator character to use, null treated as ""
     * @return the joined String, <code>null</code> if null array input
     */
    public static String join(Object[] array, String separator) {
        if (array == null) {
            return null;
        }
        return join(array, separator, 0, array.length);
    }

    /**
     * <i>Note: This method is copied from {@link org.apache.commons.lang.StringUtils} due to the nature of GWT Emulated
     * JRE.</i>
     * <p>
     * Joins the elements of the provided array into a single String containing the provided list of elements.
     * </p>
     * 
     * <p>
     * No delimiter is added before or after the list. A <code>null</code> separator is the same as an empty String
     * (""). Null objects or empty strings within the array are represented by empty strings.
     * </p>
     * 
     * <pre>
     * StringUtils.join(null, *)                = null
     * StringUtils.join([], *)                  = ""
     * StringUtils.join([null], *)              = ""
     * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
     * StringUtils.join(["a", "b", "c"], null)  = "abc"
     * StringUtils.join(["a", "b", "c"], "")    = "abc"
     * StringUtils.join([null, "", "a"], ',')   = ",,a"
     * </pre>
     * 
     * @param array
     *            the array of values to join together, may be null
     * @param separator
     *            the separator character to use, null treated as ""
     * @param startIndex
     *            the first index to start joining from. It is an error to pass in an end index past the end of the
     *            array
     * @param endIndex
     *            the index to stop joining from (exclusive). It is an error to pass in an end index past the end of the
     *            array
     * @return the joined String, <code>null</code> if null array input
     */
    public static String join(Object[] array, String separator, int startIndex, int endIndex) {
        if (array == null) {
            return null;
        }
        if (separator == null) {
            separator = EMPTY;
        }

        // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator))
        // (Assuming that all Strings are roughly equally long)
        int bufSize = (endIndex - startIndex);
        if (bufSize <= 0) {
            return EMPTY;
        }

        bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + separator.length());

        StringBuffer buf = new StringBuffer(bufSize);

        for (int i = startIndex; i < endIndex; i++) {
            if (i > startIndex) {
                buf.append(separator);
            }
            if (array[i] != null) {
                buf.append(array[i]);
            }
        }
        return buf.toString();
    }

    /**
     * For each character in the input, if it is an accent character, then accent will be remove of the character and
     * resulting the ASCII-equivalent character.
     * 
     * @param str
     *            Source string.
     * @return Converted string.
     */
    public static String removeAccents(String str) {
        return replaceChars(str, accentChars, nonAccentChars);
    }
}
